#include <csugl.h>

#include "PTCamera.h"

#include <iostream>

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glog/logging.h>

#include <imgui.h>
#include <backends/imgui_impl_glfw.h>
#include <backends/imgui_impl_opengl3.h>

#include <glm/gtc/type_ptr.hpp>

#include <stb_image.h>

struct Texture{
    // MTextureType type;
    int w, h, ch;
    uint32_t tex_ogl_id;
    
    // load from image file
    explicit Texture(const std::string& fileName){
        auto meta_data = stbi_load(fileName.c_str(), &w, &h, &ch, 0);
        init(meta_data);
    }

    Texture(int width, int height, int channel = 3, unsigned char *data = nullptr)
        : w(width), h(height), ch(channel) {
        init(data);
    }

    void bind() { glBindTexture(GL_TEXTURE_2D, tex_ogl_id); }

    private:
    void init(unsigned char* data) {
        glGenTextures(1, &tex_ogl_id);
        glBindTexture(GL_TEXTURE_2D, tex_ogl_id);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        if (ch == 3)
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
        else if (ch == 4)
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
        else if (ch == 1)
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, data);
        // gen mipmap
        glGenerateMipmap(GL_TEXTURE_2D);
    }
};

struct FrameBuffer {
    FrameBuffer(const glm::ivec2& target_size, unsigned int tex_ogl_id) {
        glGenFramebuffers(1, &fbo);
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex_ogl_id, 0);
        glGenRenderbuffers(1, &rbo);
        glBindRenderbuffer(GL_RENDERBUFFER, rbo);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, target_size.x, target_size.y);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
        if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
            LOG(ERROR) << "FRAMEBUFFER:: Framebuffer is not complete!";
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }

    inline void bind() const { glBindFramebuffer(GL_FRAMEBUFFER, fbo); }

    inline void unbind() const { glBindFramebuffer(GL_FRAMEBUFFER, 0); }

private:
    unsigned int fbo;
    unsigned int rbo;
};

struct Quad {
    Quad() {
        float vertices[] = {
            -1.0f, -1.0f, 0.0f, .0f, .0f,
            -1.0f, 1.0f, 0.0f,  .0f, 1.f,
            1.0f, -1.0f, 0.0f,  1.f, .0f,
            1.0f, 1.0f, 0.0f,   1.f, 1.f
        };
        unsigned int indices[] = {
            0, 1, 2,
            2, 1, 3,
        };
        va = csugl::VertexArray::Creat();
        vb = csugl::VertexBuffer::Create(5 * 4, vertices);
        va->addVertexAttribution(vb, {
            {csugl::BufferElementType::Float3},
            {csugl::BufferElementType::Float2},
        });
        eb = csugl::IndexBuffer::Create(3 * 2, indices);
        va->bindIndexBuffer(eb);
    }
    void draw(const csugl::Shader& shader) {
        va->bind();
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (const void *)0);
    }
    csugl::Ref<csugl::VertexArray> va;
    csugl::Ref<csugl::VertexBuffer> vb;
    csugl::Ref<csugl::IndexBuffer> eb;
};

enum class MatType{
    DIFFUSE = 0,
    GLOSSY = 1,
    EMISSION = 2,
};

struct Material {
    MatType type;
    glm::vec3 albedo;
    float roughness;
    float intensity;
    Material(MatType type, const glm::vec3 &albedo)
        : type(type), albedo(albedo), roughness(0.0f), intensity(1.0f) {}
};

enum class PrimitiveType {
    SPHERE = 0,
    TRIANGLE = 1,
};

struct Primitive {
    PrimitiveType type;
    csugl::Ref<csugl::Transform> trans;
    csugl::Ref<Material> mat;
    // unsigned int mat_id;
    Primitive(csugl::Ref<csugl::Transform> trans,
              csugl::Ref<Material> mat,  PrimitiveType type)
        : trans(trans), mat(mat), type(type) {}
};

struct Sphere : public Primitive {
    Sphere(csugl::Ref<csugl::Transform> trans, csugl::Ref<Material> mat)
        : Primitive(trans, mat, PrimitiveType::SPHERE) {}
};

struct Triangle : public Primitive {
    glm::vec3 vertex[3];
    Triangle(const glm::vec3 vert[3], csugl::Ref<csugl::Transform> trans, csugl::Ref<Material> mat)
        : Primitive(trans, mat, PrimitiveType::TRIANGLE) {
        memcpy(vertex, vert, 3 * sizeof(glm::vec3));
    }
};
const uint32_t MAX_PRIMITIVES_NUM = 20;
struct PrimitiveInfo {
    bool is_active;
    csugl::Ref<Primitive> primitive;
    glm::vec3 rotation;
    PrimitiveInfo(csugl::Ref<Primitive> pri)
        : is_active(true), primitive(pri), rotation(glm::vec3{0.0f})
    {
    }
    void update() {
        primitive->trans->rotation = rotation;
    }
};

struct DiffuseAreaLight {
};

int main(int argc, char const *argv[]) {
    std::cout << R"(   ___________ __  __________ 
  / ____/ ___// / / / ____/ / 
 / /    \__ \/ / / / / __/ /  
/ /___ ___/ / /_/ / /_/ / /___
\____//____/\____/\____/_____/
)";

    FLAGS_stderrthreshold = 0;
    google::InitGoogleLogging(argv[0]);

    auto app = csugl::singleton<csugl::Application>::getInstance();
    auto window = &app->GetWindow();

    auto shader_in = csugl::MakeRef<csugl::Shader>("../assets/shader/kpt.glsl");
    auto shader_buffer = csugl::MakeRef<csugl::Shader>("../assets/shader/buffer.glsl");

    Quad quad;

    auto camera = csugl::MakeRef<PTCamera>(glm::vec3{0.0f, 0.5f, 3.0f}, glm::radians(60.0f), 0.01f, 1000.0f);

    ImGui::CreateContext();
    ImGuiIO &io = ImGui::GetIO();
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
    io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
    io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
    (void) io;
    ImGui_ImplGlfw_InitForOpenGL(window->GetGLFWwindow(), true);
    ImGui_ImplOpenGL3_Init("#version 330 core");
    ImGui::StyleColorsDark();
    ImGuiStyle& style = ImGui::GetStyle();
    if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
        style.WindowRounding = 0.0f;
        style.Colors[ImGuiCol_WindowBg].w = 1.0f;
    }
    // ImGui::LoadIniSettingsFromDisk("../assets/imgui/imgui.ini");

    glm::ivec2 tar_size{1920, 1080};
    Texture tar_tex_in(tar_size.x, tar_size.y, 3);
    tar_tex_in.bind();
    auto fbo_in = csugl::MakeRef<FrameBuffer>(static_cast<glm::vec2>(tar_size), tar_tex_in.tex_ogl_id);

    Texture tar_tex_out(tar_size.x, tar_size.y, 3);
    tar_tex_out.bind();
    auto fbo_out = csugl::MakeRef<FrameBuffer>(static_cast<glm::vec2>(tar_size), tar_tex_out.tex_ogl_id);

    std::vector<PrimitiveInfo> pri_infos;
    {   // down
        glm::vec3 vert[3] = {{0.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}};
        auto trans1 = csugl::MakeRef<csugl::Transform>(glm::vec3(-1.5f, -1.0f, -2.0f), glm::vec3(0.0f), glm::vec3(3.0f, 1.0f, 3.0f));
        auto trans2 = csugl::MakeRef<csugl::Transform>(glm::vec3(1.5f, -1.0f, 1.0f), glm::vec3(0.0f), glm::vec3(-3.0f, 1.0f, -3.0f));
        csugl::Ref<Primitive> pri1, pri2;
        csugl::Ref<Material> mat;
        mat = csugl::MakeRef<Material>(MatType::DIFFUSE, glm::vec3{1.0f, 1.0f, 1.0f});
        pri1 = csugl::MakeRef<Triangle>(vert, trans1, mat);
        pri2 = csugl::MakeRef<Triangle>(vert, trans2, mat);
        pri_infos.push_back(PrimitiveInfo(pri1));
        pri_infos.push_back(PrimitiveInfo(pri2));
        // up
        auto trans3 = csugl::MakeRef<csugl::Transform>(glm::vec3(-1.5f, 2.0f, -2.0f), glm::vec3(0.0f), glm::vec3(3.0f, 1.0f, 3.0f));
        auto trans4 = csugl::MakeRef<csugl::Transform>(glm::vec3(1.5f, 2.0f, 1.0f), glm::vec3(0.0f), glm::vec3(-3.0f, 1.0f, -3.0f));
        csugl::Ref<Primitive> pri3, pri4;
        pri3 = csugl::MakeRef<Triangle>(vert, trans3, mat);
        pri4 = csugl::MakeRef<Triangle>(vert, trans4, mat);
        pri_infos.push_back(PrimitiveInfo(pri3));
        pri_infos.push_back(PrimitiveInfo(pri4));
    }
    {   // left
        glm::vec3 vert[3] = {{0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, -1.0f}, {0.0f, 1.0f, 0.0f}};
        auto trans1 = csugl::MakeRef<csugl::Transform>(glm::vec3(-1.5f, -1.0f, 1.0f), glm::vec3(0.0f), glm::vec3(1.0f, 3.0f, 3.0f));
        auto trans2 = csugl::MakeRef<csugl::Transform>(glm::vec3(-1.5f, 2.0f, -2.0f), glm::vec3(0.0f), glm::vec3(1.0f, -3.0f, -3.0f));
        csugl::Ref<Primitive> pri1, pri2;
        csugl::Ref<Material> mat1, mat2;
        mat1 = csugl::MakeRef<Material>(MatType::DIFFUSE, glm::vec3{0.0f, 1.0f, 0.0f});
        mat2 = csugl::MakeRef<Material>(MatType::DIFFUSE, glm::vec3{1.0f, 0.0f, 0.0f});
        pri1 = csugl::MakeRef<Triangle>(vert, trans1, mat1);
        pri2 = csugl::MakeRef<Triangle>(vert, trans2, mat1);
        pri_infos.push_back(PrimitiveInfo(pri1));
        pri_infos.push_back(PrimitiveInfo(pri2));
        // right
        auto trans3 = csugl::MakeRef<csugl::Transform>(glm::vec3(1.5f, -1.0f, 1.0f), glm::vec3(0.0f), glm::vec3(1.0f, 3.0f, 3.0f));
        auto trans4 = csugl::MakeRef<csugl::Transform>(glm::vec3(1.5f, 2.0f, -2.0f), glm::vec3(0.0f), glm::vec3(1.0f, -3.0f, -3.0f));
        csugl::Ref<Primitive> pri3, pri4;
        pri3 = csugl::MakeRef<Triangle>(vert, trans3, mat2);
        pri4 = csugl::MakeRef<Triangle>(vert, trans4, mat2);
        pri_infos.push_back(PrimitiveInfo(pri3));
        pri_infos.push_back(PrimitiveInfo(pri4));
    }
    {   // back
        glm::vec3 vert[3] = {{0.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}};
        auto trans1 = csugl::MakeRef<csugl::Transform>(glm::vec3(-1.5f, -1.0f, -2.0f), glm::vec3(0.0f), glm::vec3(3.0f, 3.0f, 1.0f));
        auto trans2 = csugl::MakeRef<csugl::Transform>(glm::vec3(1.5f, 2.0f, -2.0f), glm::vec3(0.0f), glm::vec3(-3.0f, -3.0f, 1.0f));
        csugl::Ref<Primitive> pri1;
        csugl::Ref<Primitive> pri2;
        csugl::Ref<Material> mat;
        mat = csugl::MakeRef<Material>(MatType::DIFFUSE, glm::vec3{1.0f, 1.0f, 1.0f});
        pri1 = csugl::MakeRef<Triangle>(vert, trans1, mat);
        pri2 = csugl::MakeRef<Triangle>(vert, trans2, mat);
        pri_infos.push_back(PrimitiveInfo(pri1));
        pri_infos.push_back(PrimitiveInfo(pri2));
    }

    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    float start_time = static_cast<float>(glfwGetTime());
    float delta_time;

    int cur_frame = 0;
    int true_frame = 0;

    bool is_update = false;

    float fps_timer = 0.0f;
    float fps = 0.0f;

    while (app->isOpen()) {
        cur_frame++;
        true_frame++;
        // cur_frame = glm::clamp(cur_frame, 0, 1000);
        delta_time = static_cast<float>(glfwGetTime()) - start_time;
        start_time = static_cast<float>(glfwGetTime());
        camera->moveSpeed = delta_time * 3.0f;
        ImGui_ImplOpenGL3_NewFrame();
        ImGui_ImplGlfw_NewFrame();
        ImGui::NewFrame();

        // ImGui::ShowDemoWindow();

        {
            static bool show_option = true;
            static bool opt_fullscreen = true;
            static bool opt_padding = false;
            static ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_None;
            ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking;
            if (opt_fullscreen)
            {
                const ImGuiViewport *viewport = ImGui::GetMainViewport();
                ImGui::SetNextWindowPos(viewport->WorkPos);
                ImGui::SetNextWindowSize(viewport->WorkSize);
                ImGui::SetNextWindowViewport(viewport->ID);
                ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
                ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
                window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
                window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
            }
            else
            {
                dockspace_flags &= ~ImGuiDockNodeFlags_PassthruCentralNode;
            }

            if (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode)
                window_flags |= ImGuiWindowFlags_NoBackground;
            if (!opt_padding)
                ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));

            ImGui::Begin("DockSpace Demo", &show_option, window_flags);
            if (!opt_padding)
                ImGui::PopStyleVar();
            if (opt_fullscreen)
                ImGui::PopStyleVar(2);

            ImGuiIO &io = ImGui::GetIO();
            if (io.ConfigFlags & ImGuiConfigFlags_DockingEnable)
            {
                ImGuiID dockspace_id = ImGui::GetID("MyDockSpace");
                ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags);
            }
            else
            {
                ImGuiIO &io = ImGui::GetIO();
                ImGui::Text("ERROR: Docking is not enabled! See Demo > Configuration.");
                ImGui::Text("Set io.ConfigFlags |= ImGuiConfigFlags_DockingEnable in your code, or ");
                ImGui::SameLine(0.0f, 0.0f);
                if (ImGui::SmallButton("click here"))
                    io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
            }

            if (ImGui::BeginMenuBar())
            {
                if (ImGui::BeginMenu("Options"))
                {
                    ImGui::MenuItem("Fullscreen", NULL, &opt_fullscreen);
                    ImGui::MenuItem("Padding", NULL, &opt_padding);
                    ImGui::Separator();

                    if (ImGui::MenuItem("Close", NULL, false, show_option))
                    {
                        show_option = false;
                        app->close();
                    }
                    ImGui::EndMenu();
                }
                ImGui::EndMenuBar();
            }

            ImGui::End();
        }

        {
            glm::vec2 target_size;
            bool is_scene_open = true;
            ImGui::Begin("SceneOut", &is_scene_open);
            camera->update();
            target_size = glm::vec2{ImGui::GetWindowSize().x, ImGui::GetWindowSize().y};
            camera->set_aspect(target_size.x / target_size.y);
            ImVec2 tar_win_size = {target_size.x, target_size.y - 36.0f};
            ImGui::Image(reinterpret_cast<ImTextureID>(tar_tex_out.tex_ogl_id), tar_win_size, {0, 1}, {1, 0});
            ImGui::End();
        }

        {
            glm::vec2 target_size;
            bool is_scene_open = true;
            ImGui::Begin("SceneIn", &is_scene_open);
            target_size = glm::vec2{ImGui::GetWindowSize().x, ImGui::GetWindowSize().y};
            ImVec2 tar_win_size = (target_size.x > target_size.y) ? ImVec2{target_size.x , target_size.x / camera->aspect - 36.0f} : ImVec2{target_size.y * camera->aspect, target_size.y - 36.0f};
            ImGui::Image(reinterpret_cast<ImTextureID>(tar_tex_in.tex_ogl_id), tar_win_size, {0, 1}, {1, 0});
            ImGui::End();
        }

        static int pri_selected = -1;
        {
            static bool is_object_open = true;
            ImGui::Begin("Primitive", &is_object_open);
            static int pri_type_combo = 0;
            static int mat_type_combo = 0;
            if(ImGui::Button("Add Primitive As") && pri_infos.size() < MAX_PRIMITIVES_NUM) {
                auto trans = csugl::MakeRef<csugl::Transform>(glm::vec3(0.0f, 1.0f, -2.0f), glm::vec3(0.0f), glm::vec3(0.5f));
                csugl::Ref<Primitive> pri = nullptr;
                csugl::Ref<Material> mat = nullptr;
                mat = csugl::MakeRef<Material>(static_cast<MatType>(mat_type_combo), glm::vec3(0.8f));
                glm::vec3 tri_vert[3] = {{0.0f, 0.0f, 0.0f}, {2.0f, 0.0f, 0.0f}, {0.0f, 2.0f, 0.0f}};
                switch(pri_type_combo) {
                    case (uint32_t)PrimitiveType::SPHERE:
                        pri = csugl::MakeRef<Sphere>(trans, mat);
                        break;
                    case (uint32_t)PrimitiveType::TRIANGLE:
                        pri = csugl::MakeRef<Triangle>(tri_vert, trans, mat);
                        break;
                }
                if (pri) {
                    pri_infos.push_back(PrimitiveInfo(pri));
                    pri_selected = pri_infos.size() - 1;
                }
            }
            if(ImGui::Button("Remove") && pri_infos.size() > 0 && pri_selected >= 0) {
                pri_infos.erase(pri_infos.begin() + pri_selected);
                pri_selected = pri_selected - 1;
            }
            ImGui::Combo("Primitive##1",&pri_type_combo,"Sphere\0Triangle\0");
            ImGui::Combo("Material##1",&mat_type_combo,"Diffuse\0Glossy\0Emission\0");
            ImGui::Separator();
            
            ImGui::Text("Primitives List");
            {
                for (int n = 0; n < pri_infos.size(); n++) {
                    std::stringstream buf;
                    switch(pri_infos[n].primitive->type){
                        case PrimitiveType::SPHERE: buf << "Sphere"; break;
                        case PrimitiveType::TRIANGLE: buf << "Triangle"; break;
                    }
                    buf << std::to_string(n);// << " " << std::to_string(n);
                    if (ImGui::Selectable(buf.str().c_str(), pri_selected == n))
                        pri_selected = n;
                }
            }
            ImGui::End();
        }

        static int spp = 1;
        static float p_RR= 0.5f;
        static bool is_sky_box = true;
        {
            static bool is_option_open = true;
            ImGui::Begin("Path Tracer Option");
            ImGui::SliderInt("SPP", &spp, 1, 32);
            ImGui::SliderFloat("P_RR", &p_RR, 0.01f, 0.91f);
            ImGui::Checkbox("Skybox", &is_sky_box);
            ImGui::End();
        }

        {
            float dt = csugl::LowpTime::deltaTime();
            if (fps_timer > 1.f) 
            {
                fps_timer = 0.f;
                fps = 1.f / dt;
            }
            fps_timer += dt;
            static bool is_status_open = true;
            ImGui::Begin("Path Tracer Status");
            ImGui::Text("Current Frame: %d", cur_frame);
            ImGui::Text("FPS: %.1f", fps);
            ImGui::End();
        }

        {
            static bool is_prop_open = true;
            ImGui::Begin("Primitive Properties", &is_prop_open);
            if (pri_selected >= 0) {
                auto &cur_pri = pri_infos[pri_selected];
                ImGui::Text("Transform");
                ImGui::Checkbox("Enable", &cur_pri.is_active);
                ImGui::DragFloat3("Position", glm::value_ptr(cur_pri.primitive->trans->position), 0.011f, -1.e5f, 1.e5f, "%.2f");
                ImGui::DragFloat3("Rotation", glm::value_ptr(cur_pri.rotation), 0.003142f, -1.e5f, 1.e5f, "%.2f");
                ImGui::DragFloat3("Scale", glm::value_ptr(cur_pri.primitive->trans->scale), 0.011f, -1.e5f, 1.e5f, "%.2f");
                ImGui::Separator();
                ImGui::Text("Material");
                ImGui::Text("Material ID: %d", pri_selected);
                ImGui::ColorEdit3("Albedo", glm::value_ptr(cur_pri.primitive->mat->albedo));
                if(cur_pri.primitive->mat->type == MatType::GLOSSY) {
                    ImGui::SliderFloat("Roughness", &cur_pri.primitive->mat->roughness, 0.0f, 1.0f, "%.2f");
                } else if(cur_pri.primitive->mat->type == MatType::EMISSION) {
                    ImGui::SliderFloat("Intensity", &cur_pri.primitive->mat->intensity, 0.0f, 20.0f, "%.2f");
                }
                cur_pri.update();
            }
            ImGui::End();
        }

        // render pass 1
        {
            fbo_in->bind();
            glViewport(0, 0, (GLsizei)tar_size.x, (GLsizei)tar_size.y);
            glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

            shader_in->use();

            shader_in->set_int("_frame", true_frame);
            float rdSeed[4] = {(float)(rand() % 1000),(float)(rand() % 1000),(float)(rand() % 1000),(float)(rand() % 1000)};
            shader_in->set_floatArray("_rdSeed", 4, rdSeed);
            shader_in->set_vec2("_raster_size", glm::vec2{tar_size.x, tar_size.y});

            glm::vec3 lb, hor, ver;
            camera->GetLBHV(lb, hor, ver);
            shader_in->set_vec3("_camera.position", camera->origin);
            shader_in->set_vec3("_camera.left_bottom", lb);
            shader_in->set_vec3("_camera.horizontal", hor);
            shader_in->set_vec3("_camera.vertical", ver);
            // pt option
            shader_in->set_int("_spp", spp);
            shader_in->set_float("_p_RR", p_RR);
            shader_in->set_float("_skybox", is_sky_box);
            // primitive option
            shader_in->set_int("_primitive_num", pri_infos.size());
            for (int i = 0; i < pri_infos.size(); i++) {
                const auto& cur_pri = pri_infos[i];
                std::stringstream pri_str;
                pri_str << "_primitives[";
                auto i_str = std::to_string(i);
                pri_str << i_str;
                shader_in->set_bool(pri_str.str() + "].is_active", cur_pri.is_active);
                shader_in->set_mat4(pri_str.str() + "].trans", cur_pri.primitive->trans->get_trans_mat());
                shader_in->set_mat4(pri_str.str() + "].trans_inv", cur_pri.primitive->trans->get_inv_trans());
                shader_in->set_int(pri_str.str() + "].mat_id", i);
                if (cur_pri.primitive->type == PrimitiveType::TRIANGLE) {
                    shader_in->set_int(pri_str.str() + "].type", static_cast<uint32_t>(PrimitiveType::TRIANGLE));
                    auto tri = static_cast<Triangle *>(cur_pri.primitive.get());
                    shader_in->set_vec3(pri_str.str() + "].va", tri->vertex[0]);
                    shader_in->set_vec3(pri_str.str() + "].vb", tri->vertex[1]);
                    shader_in->set_vec3(pri_str.str() + "].vc", tri->vertex[2]);
                } else {
                    shader_in->set_int(pri_str.str() + "].type", static_cast<uint32_t>(PrimitiveType::SPHERE));
                }
                std::stringstream mat_str;
                mat_str << "_materials[" << i_str;
                shader_in->set_int(mat_str.str() + "].type", static_cast<uint32_t>(cur_pri.primitive->mat->type));
                shader_in->set_vec3(mat_str.str() + "].albedo", cur_pri.primitive->mat->albedo);
                shader_in->set_float(mat_str.str() + "].roughness", cur_pri.primitive->mat->roughness);
                shader_in->set_float(mat_str.str() + "].intensity", cur_pri.primitive->mat->intensity);
            }
            quad.draw(*shader_in);
        }
        
        // render pass 2
        {
            fbo_out->bind();
            glViewport(0, 0, (GLsizei)tar_size.x, (GLsizei)tar_size.y);
            glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
            if (camera->is_updated())
            {
                cur_frame = 1;
                glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
            }
            else
            glClear(GL_DEPTH_BUFFER_BIT);

            shader_buffer->use();

            shader_buffer->set_int("_frame", cur_frame);

            glActiveTexture(GL_TEXTURE0);
            shader_buffer->set_sampler2D("_tIn", 0);
            tar_tex_in.bind();


            glActiveTexture(GL_TEXTURE0 + 1);
            shader_buffer->set_sampler2D("_tOut", 1);
            tar_tex_out.bind();
            
            quad.draw(*shader_buffer);
        }

        // render pass 3
        {
            fbo_out->unbind();
            glViewport(0, 0, (GLsizei)window->GetSize().x, (GLsizei)window->GetSize().y);
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
            glClearColor(0.4f, 0.5f, 0.2f, 1.0f);

            ImGui::Render();
            ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());

            io = ImGui::GetIO();
            if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
                ImGui::UpdatePlatformWindows();
                ImGui::RenderPlatformWindowsDefault();
                glfwMakeContextCurrent(window->GetGLFWwindow());
            }
        }

        window->Display();

        csugl::LowpTime::update();
    }

    ImGui_ImplOpenGL3_Shutdown();
    ImGui_ImplGlfw_Shutdown();
    // ImGui::SaveIniSettingsToDisk("../assets/imgui/imgui.ini");
    ImGui::DestroyContext();

    csugl::singleton<csugl::Application>::destroy();
    return 0;
}
